package me.itsoo.artemis.framework.core.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * UriUtils
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/1
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UriUtils {

    /**
     * URI 编码
     *
     * @param value Object
     * @return String
     */
    public static String encode(Object value) {
        return (value == null || StringUtils.isEmpty(value.toString()))
                ? StringUtils.EMPTY_RESULT
                : encodeUriComponent(value.toString(), StandardCharsets.UTF_8);
    }

    /**
     * URI 编码
     *
     * @param source  String
     * @param charset Charset
     * @return String
     */
    public static String encode(String source, Charset charset) {
        return encodeUriComponent(source, charset);
    }

    private static String encodeUriComponent(String source, Charset charset) {
        try {
            if (StringUtils.isEmpty(source)) {
                return source;
            }

            final byte[] bytes = source.getBytes(charset);
            final ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length);
            return write(bytes, bos) ? bos.toString(charset.name()) : source;
        } catch (Exception e) {
            return null;
        }
    }

    private static boolean write(byte[] bytes, ByteArrayOutputStream bos) {
        boolean result = false;

        for (byte b : bytes) {
            if (b < 0) {
                b = (byte) (b + 256);
            }

            if (isAllowed(b)) {
                bos.write(b);
            } else {
                bos.write('%');
                bos.write(Character.toUpperCase(Character.forDigit(b >> 4 & 15, 16)));
                bos.write(Character.toUpperCase(Character.forDigit(b & 15, 16)));
                result = true;
            }
        }

        return result;
    }

    private static boolean isAllowed(int c) {
        return isAlpha(c) || isDigit(c) || ('-' == c) || ('.' == c) || ('_' == c) || ('~' == c);
    }

    private static boolean isAlpha(int c) {
        return (c >= 'a') && (c <= 'z') || (c >= 'A') && (c <= 'Z');
    }

    private static boolean isDigit(int c) {
        return (c >= '0') && (c <= '9');
    }
}
